package com.glodon.paas.document.storage.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.UUID;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

import com.glodon.paas.document.storage.id.ObjectId;
import com.glodon.paas.document.storage.id.ObjectResult;

public class StorageUtil {

    private static final String SALT = "haha";
    private static final boolean DEFAULT_ENCRYPT = false;

    public static ObjectResult toOut(InputStream in, OutputStream out) throws IOException {
        return toOut(in, out, new DigestState().getMessageDigest(), DEFAULT_ENCRYPT);
    }

    public static ObjectResult toOut(InputStream in, OutputStream out, MessageDigest md) throws IOException {
        return toOut(in, out, md, DEFAULT_ENCRYPT);
    }

    public static ObjectResult toOut(InputStream in, OutputStream out, MessageDigest md, boolean encrypt)
            throws IOException {
        OutputStream dOut = getStorageOutputStream(out, md, encrypt);
        long length = IOUtils.copyLarge(in, dOut);
        dOut.close();
        DigestState digestState = DigestState.getDigestState(md);
        byte[] digest = md.digest();
        String objectId = ObjectId.fromRaw(digest).name();
        return new ObjectResult(objectId, length, digestState);
    }

    private static OutputStream getStorageOutputStream(OutputStream out, MessageDigest md, boolean encrypt) {
        if (!encrypt)
            return new DigestOutputStream(out, md);
        CipherOutputStream eOut = getEncryptOutputStream(out);
        DeflaterOutputStream cOut = new DeflaterOutputStream(eOut);
        return new DigestOutputStream(cOut, md);
    }

    private static CipherOutputStream getEncryptOutputStream(OutputStream os) {
        Cipher cp = getCipher(Cipher.ENCRYPT_MODE);
        return new CipherOutputStream(os, cp);
    }

    private static Cipher getCipher(int cipherMode) {
        try {
            byte[] key = Arrays.copyOf(DigestUtils.sha(SALT.getBytes("UTF-8")), 16);
            SecretKey secretKey = new SecretKeySpec(key, "AES");
            Cipher cp = Cipher.getInstance("AES");
            cp.init(cipherMode, secretKey);
            return cp;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static InputStream getStorageInputStream(InputStream in) {
        return getStorageInputStream(in, DEFAULT_ENCRYPT);
    }

    public static InputStream getStorageInputStream(InputStream in, boolean encrypt) {
        if (!encrypt)
            return in;
        Cipher cp = getCipher(Cipher.DECRYPT_MODE);
        CipherInputStream is = new CipherInputStream(in, cp);
        InflaterInputStream iIn = new InflaterInputStream(is);
        return iIn;
    }

    public static String generateUUID() {
        return StringUtils.replace(UUID.randomUUID().toString(), "-", "");
    }
}
